-
Notifications
You must be signed in to change notification settings - Fork 729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix devirtualization issues with TR_RelocationRecord/IlGeneratorMethodDetails #1682
Conversation
@mstoodle Mark, could you please review the first commit related to relocation records? There will be another commit (under the same PR) for IlGeneratorMethodDetails. Thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will work as is, but I think the code can be improved and only one of the recommended changes could be called "onerous" but hopefully still not too bad...
|
||
static TR_RelocationRecord *create(TR_RelocationRecord *storage, TR_RelocationRuntime *reloRuntime, TR_RelocationTarget *reloTarget, TR_RelocationRecordBinaryTemplate *recordPointer) | ||
{ | ||
return decode(storage, reloRuntime, reloTarget, recordPointer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just fold decode() into create and keep create() out of the header? Nothing else calls decode, I don't think, so the extra call doesn't seem valuable to me.
break; | ||
default: | ||
// TODO: error condition | ||
printf("Unexpected relo record: %d\n", reloType);fflush(stdout); | ||
TR_ASSERT(0, "Unexpected relocation record type found"); | ||
exit(0); | ||
} | ||
reloRecord->_reloRuntime = reloRuntime; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more code to change, but adding these fields to the constructors for all the relocation classes (which pass them into the base class constructor) to initialize would be a lot cleaner. Then you don't need to add the "setBinaryRecord()" function which isn't really something RelocationRecord should be allowing (especially as a public function).
@@ -216,6 +219,9 @@ class TR_RelocationRecord | |||
TR_RelocationRecordBinaryTemplate *_record; | |||
|
|||
TR_RelocationRecordPrivateData _privateData; | |||
private: | |||
static TR_RelocationRecord * decode(TR_RelocationRecord *storage, TR_RelocationRuntime *reloRuntime, TR_RelocationTarget *reloTarget, TR_RelocationRecordBinaryTemplate *record); | |||
static uint8_t getReloType(TR_RelocationTarget *reloTarget, TR_RelocationRecordBinaryTemplate *rec); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getReloType() is probably better implemented as a nonvirtual instance function on the TR_RelocationRecordBinaryTemplate struct. That's somewhat out of character for those structs, but I like that better than adding this function here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getReloType() is used in a static function, so it has to be static as well (not an instance function). I could move it to TR_RelocationRecordBinaryTemplate, but still as a static function.
d920d4d
to
50dc81e
Compare
@mstoodle I have addressed the suggestions from the review. |
When processing relocation records the JIT allocates a generic TR_RelocationRecord on the stack and then morphs this object into a specialized variety using placement new. The expectation is that any call off the newly created object is going to use the vtable to invoke the specific code associated with a specialized/derived relocation record. However, the C++ compiler is allowed to devirtualize calls and if that occurs, the JIT is going to invoke the implementation in the generic TR_RelocationRecord. The solution introduced by this commit is to use an object factory that builds a specific relocation record at a designated address and returns a pointer to that address. The newly created object must be accessed only through the pointer returned by the object factory. Signed-off-by: Marius Pirvu <[email protected]>
Looks good for this first change. Thanks, @mpirvu ! |
1216c84
to
2c9920a
Compare
@@ -50,7 +50,7 @@ TR_MethodToBeCompiled *TR_MethodToBeCompiled::allocate(J9JITConfig *jitConfig) | |||
|
|||
void TR_MethodToBeCompiled::initialize(TR::IlGeneratorMethodDetails & details, void *oldStartPC, CompilationPriority p, TR_OptimizationPlan *optimizationPlan) | |||
{ | |||
_methodDetails = details; | |||
_methodDetails = J9::IlGeneratorMethodDetails::create(_methodDetailsStorage, details); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not referenced J9::IlGeneratorMethodDetails directly. Only TR::IlGeneratorMethodDetails should be referenced outside of its class hierarchy.
You'll need to expose a static create() call in the TR:: version that directs down to the J9 static implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that for code within the trj9
folder it was ok to use J9::
, or is it because IlGeneratorMethodDetails
is extensible that we have to use TR::
. But then I see instances in CompilationThread.cpp of J9::NewInstanceThunkDetails
instead of TR::NewInstanceThunkDetails
.
@@ -50,8 +50,8 @@ class OMR_EXTENSIBLE IlGeneratorMethodDetails : public J9::IlGeneratorMethodDeta | |||
IlGeneratorMethodDetails(const TR::IlGeneratorMethodDetails & other) : | |||
J9::IlGeneratorMethodDetailsConnector(other) {} | |||
|
|||
IlGeneratorMethodDetails & operator=(const TR::IlGeneratorMethodDetails & other); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you want that create function, you'll need to declare it here
} | ||
|
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...and you'll need to implement create() here to call J9::IlGeneratorMethodDetails::create()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's exactly what I did, but I hit a strange compilation error in trj9/env/j9method.cpp when using the other create function:
TR::IlGeneratorMethodDetails & details = TR::IlGeneratorMethodDetails::create(storage, this);
It's like the compiler cannot distinguish between
inline static TR::IlGeneratorMethodDetails & create(TR::IlGeneratorMethodDetails & target, TR_ResolvedMethod *method);
and
static IlGeneratorMethodDetails *create(TR::IlGeneratorMethodDetails &storage, const TR::IlGeneratorMethodDetails & other);
I thought that TR_ResolvedMethod *method
was considered different than TR::IlGeneratorMethodDetails & other
Changing the name of the second create function to something else solves the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if you need a using J9::IlGeneratorMethodDetailsConnector::create;
in your J9IlGeneratorMethodDetails.hpp. We've seen this before with extensible classes where we're directing the compiler to search for a method from the leaves of the hierarchy upward, and if there are two or more methods with the same name but different signatures this instructs the compiler to keep searching if it encounters a signature that doesn't match. I don't recall the name of this C++ phenomenon. See compiler/x/codegen/OMRCodeGenerator.hpp
where we use it for canNullChkBeImplicit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I am still going to change the name of function I added to 'clone' because it better reflects what it does: it takes an object and it allocates an identical object at the specified address.
Jenkins test all |
Off the 6 builds that failed, 2 are connection issues, 2 are disk space issues and for two (JDK8 lnx 390/ppc) I see java.io.FileNotFoundException. |
I verified manually on a Ubuntu 16.04 box (with gcc 5.4.0) that, before this fix, DLT methods were seen as OrdinaryMethods; after the fix they are correctly identified as MethodInProgress. |
@mstoodle Mark, the code is ready to be merged, unless you would like more modifications. Thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still a couple things to clean up, please!
{ | ||
return self()->J9::IlGeneratorMethodDetailsConnector::operator=(other); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious why this particular forwarding function has been put into the cpp file, which means little chance it will ever be inlined...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept the old structure: wherever operator=
was present I replaced it with the new clone function. You are right, it's better to move it in the hpp file. Done.
@@ -74,10 +74,10 @@ class OMR_EXTENSIBLE IlGeneratorMethodDetails : public OMR::IlGeneratorMethodDet | |||
|
|||
IlGeneratorMethodDetails(const TR::IlGeneratorMethodDetails & other); | |||
|
|||
TR::IlGeneratorMethodDetails & operator=(const TR::IlGeneratorMethodDetails & other); | |||
|
|||
static TR::IlGeneratorMethodDetails & create(TR::IlGeneratorMethodDetails & target, TR_ResolvedMethod *method); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is no longer any create() implementation, is there? this declaration should be removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the old create() function that is used in two places in the code. Note that the second parameter is a TR_ResolvedMethod.
When storing a particular variety of IlGeneratorMethodDetails into MethodToBeCompiled, the code uses operator = which transmutes the existing generic IlGeneratorMethodDetails (the base class) into a derived class similar to the parameter that is passed in, by using placement new. The expectation is that any call off the generic IlGeneratorMethodDetails object stored in MethodToBeCompiled is going to be a virtual call. However, the C++ compiler is allowed to devirtualize calls and if that occurs, the JIT is going to invoke the implementation in the generic IlGeneratorMethodDetails. The solution introduced by this commit is to use an object factory that builds a specific IlGeneratorMethodDetails at a designated address and returns a pointer to that address which is then stored in MethodToBeCompiled. Signed-off-by: Marius Pirvu <[email protected]>
I removed the clone() method from TR::IlGeneratorMethodDetails so that it can call directly the implementation from J9::IlGeneratorMethodDetails through inheritance. |
jenkins test sanity |
Great set of changes; thanks @mpirvu for fixing these long standing obstacles! |
Time to warm up gcc 7 and see if we get any throughput improvements? |
Throughput no, but maybe, maybe some startup time improvements from lower compilation time due to better devirtualization. |
Compiling OpenJ9 with a more recent compiler (e.g. gcc 5.x) can expose some
long existing bugs in two parts of the code:
(1) Allocating a TR_RelocationRecord and morphing it into a specialized
relocation record
(2) Copying an IlGeneratorMethodDetails object in
TR_MethodToBeCompiled::initialize()
In both cases the code constructs an object of a derived class on top of an
object of a base class using
placement new
. Of course, for this to have anychance of success, the size of the two objects, base and derived, must be the
same and we make sure that indeed that's the case.
However, the code suffers from a subtle problem: the C++ compiler sees an
object of a base class being instantiated and it's free to devirtualize calls
off such an object, leading to the wrong code to be executed.
For clarification, let's use a generic example:
The pattern used here is that of an object factory: based on some input we
want to create an object of a specific derived type, but return a pointer to
the base class so that it can be handled through virtual functions.
The difference from a traditional object factory is that we use placement
new to construct on top of an existing object (rather than allocating a brand
new object).
For the relocation record case mentioned above this approach has the advantage
of allocating a single TR_RelocationRecord on the stack and reusing that
storage for every record that the code needs to process.
For the ILGeneratorMethodDetails case this approach is somewhat forced by our
decision to embed such an object into TR_MethodToBeCompiled.
The simplest fix for the existing bugs is to use the object factory pattern
and allowing it to construct at a designated address, but always use the value
returned by the factory when calling virtual functions.
The example above could be modified along the following lines: